#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <mutex>
#include <cassert>
#include <fstream>
#include<algorithm>
#include "../comm/util.hpp"
#include "../comm/log.hpp"
#include"../comm/httplib.h"
#include "oj_model.hpp"
// #include "./oj_model2.hpp"

#include "oj_view.hpp"
#include<jsoncpp/json/json.h>


namespace ns_control
{
   using namespace ns_util;
   using namespace ns_log;
   using namespace ns_model;
   using namespace ns_view;
   using namespace httplib;

   // 一个编译对应一台机器 
   //提供服务的主机
   class Machine
   {
   public:
      std::string _ip;  // 编译服务的ip
      int _prot;        // 编译服务的端口
      uint64_t _load;   // 编译服务的负载
      std::mutex *_mtx; // 禁止拷贝,使用指针

   public:
      Machine()
          : _ip(""), _prot(0), _load(0), _mtx(nullptr)
      {
      }
      ~Machine() {}
   public:
      //提升主机负载
      void IncLoad()
      {
         if(_mtx) _mtx->lock();
         ++_load;
         if(_mtx) _mtx->unlock();
      }
      //减少主机负载
      void DecLoad()
      {
         if(_mtx) _mtx->lock();
         --_load;
         if(_mtx) _mtx->unlock();
      }

      //获取主机负载
      uint64_t Load()
      {
         uint64_t load=0;
         if(_mtx) _mtx->lock();
         load=_load;
         if(_mtx) _mtx->unlock();
         return load;
      }

      //负载清零
      void RestLoad()
      {
          if(_mtx) _mtx->lock();
         _load=0;
         if(_mtx) _mtx->unlock();
      }

   };

   // 负载均衡模块
   const std::string service_machine = "./conf/service_machine.conf";
   class LoadBlance
   {
   private:
      /*
      每一台主机都有自己的下标,用下标充当当前主机的id
      */
      std::vector<Machine> machines; // 提供编译服务的所有主机
      std::vector<int> online;       // 在线主机id
      std::vector<int> offline;      // 离线主机id
      std::mutex mtx;                //保证负载均衡模块的安全
   public:
      LoadBlance()
      {
         assert(LoadConf(service_machine));
         LOG(INFO)<<"加载"<<service_machine<<"成功"<<"\n";
      }
      ~LoadBlance() {}

   public:
      //主机配置
      bool LoadConf(const std::string &machine_conf)
      {
         std::ifstream in(machine_conf);
         if (!in.is_open())
         {
            LOG(FATAL) << "加载:" << machine_conf << "失败"
                       << "\n";
            return false;
         }
         std::string line;
         while (std::getline(in, line))
         {
            // 127.0.0.1:8081
            std::vector<std::string> tokens;
            StringUtil::SplitString(line, &tokens, ":");
            if (tokens.size() != 2)
            {
               LOG(WARNING) << "切分主机和端口" << line << "失败"
                            << "\n";
               continue;
            }
            Machine m;
            m._ip = tokens[0];
            m._prot = atoi(tokens[1].c_str());
            m._load = 0;
            m._mtx = new std::mutex();
            online.push_back(machines.size()); // 加入在线主机
            machines.push_back(m);
         }
         in.close();
         return true;
      }

      /*
        负载均衡选择:轮训+hash的方法;
        id:主机id m:机器的地址
      */
      bool SmartChoice(int *id,Machine **m)
      {
         mtx.lock();
         //1.使用选择好的主机(更新对应主机的负载)
         int online_num=online.size();
         if(online_num==0)
         {
            mtx.unlock();
            LOG(FATAL)<<"所有后端编译主机全部离线"<<"\n";      
            return false;
         }

         //遍历在线主机,找到最小的主机
         *id = online[0];
        *m = &machines[online[0]];
        uint64_t min_load = machines[online[0]].Load();
        for (int i = 1; i < online_num; i++)
        {
            uint64_t curr_load = machines[online[i]].Load();
            if (min_load > curr_load)
            {
                min_load = curr_load;
                *id = online[i];
                *m = &machines[online[i]];
            }
        }

         mtx.unlock();
         return true;
      }

      void OfflineMachine(int which)
      {
         mtx.lock();
         for(auto iter=online.begin();iter!=online.end();iter++)
         {
            if(*iter==which)
            {
               //离线的主机找到了
               machines[which].RestLoad();
               online.erase(iter);
               offline.push_back(which);
               break;
            }
         }
         mtx.unlock();
      }

      void OnlineMachine()
      {
        
         mtx.lock();
         //当所有的主机离线的时候,统一上线
         online.insert(online.end(),offline.begin(),offline.end());  //把offline加入到online中
         offline.erase(offline.begin(),offline.end());
         mtx.unlock();
          LOG(INFO) << "所有的主机上线啦！" << "\n";
      }

      //for test
      void ShowMachines()
      {
         mtx.lock();
         std::cout<<"当前在线主机列表";
         for(auto &id:online)
         {
            std::cout<<id<<" ";
         }
         std::cout<<endl;
         std::cout<<"当前离线主机列表";
         for(auto &id:offline)
         {
            std::cout<<id<<" ";
         }
         std::cout<<endl;
         mtx.unlock();
      }
   };

   // 核心业务模块
   class Control
   {
   private:
      Model _model; // 后台数据
      View _view;   // html渲染功能
      LoadBlance _load_balance; //负载均衡
   public:
      Control() {}
      ~Control() {}

   public:
   
      void RecoverMachine()
      {
         _load_balance.OnlineMachine();
      }

      // 根据题目数据构建网页
      bool AllQuestions(string *html)
      {
         bool ret = true;
         vector<struct Question> all;
         if (_model.GetAllQuestions(&all))
         {
            sort(all.begin(),all.end(),[](const struct Question &q1,const struct Question &q2)
            {
               return atoi(q1.number.c_str()) <atoi(q2.number.c_str());
            });
            // 获取题目信息成功,将所有题目构建网页
            _view.AllExpandHtml(all, html);
         }
         else
         {
            *html = "获取题目失败,形成题目列表失败";
            ret = false;
         }
         return ret;
      }

      bool OneQuestion(const std::string &number, string *html)
      {
         Question q;
         bool ret = true;
         if (_model.GetOneQuestion(number, &q))
         {
            // 获取指定题目信息成功,将所有题目构建网页
            _view.OneExpandHtml(q, html);
            LOG(INFO) << "获取指定题目信息成功 " << std::endl;
         }
         else
         {
            *html = "指定题目" + number + "不存在";
            ret = false;
         }
         return ret;
      }

      // 用户提交的信息都在in_Json中
      void Judge(const std::string &number,const std::string in_json, std::string *out_json)
      {     
         // 0.根据题号,拿到题目细节
         struct Question q;
         _model.GetOneQuestion(number,&q);

         // 1. in_json进行反序列化得到题目id，code，input
         Json::Reader reader;
         Json::Value in_value;
         reader.parse(in_json,in_value);
         std::string code=in_value["code"].asString();

         // 2. 重新拼接用户代码+测试用例代码 形成新的代码
         Json::Value compile_value; // Json::Value可以接收任何类型
         compile_value["input"]=in_value["input"].asString();
         compile_value["code"]=code+"\n"+q.tail;
         compile_value["cpu_limit"]=q.cpu_limit;
         compile_value["mem_limit"]=q.mem_limit;
         Json::FastWriter writer;
         std::string compline_string=writer.write(compile_value);

         // 3. 选择负载最低的主机
         //    规则:一直选择,直到主机可用,否则,就是全部挂掉
         while(true)
         {
            int id=0;
            Machine *m=nullptr;
            if(!_load_balance.SmartChoice(&id,&m))
            {
               break;
            }
            

         //4.发起http请求，得到结果
         //cpp-http使用
         //Response  version,reason,body
         Client cli(m->_ip,m->_prot);
         m->IncLoad();//请求成功增加负载
          LOG(INFO)<<"选择主机成功,主机id"<<id<<" "<<m->_ip<<":"<<m->_prot<<"当前主机的负载是:"<<m->_load<<"\n";
         if(auto res=cli.Post("/compile_and_run",compline_string,"application/json;charset=utf-8"))
         {
            if(res->status ==200)
            {
               *out_json=res->body;//将结果给回out_json
               m->DecLoad();//减少主机负载
               LOG(INFO)<<"请求编译和运行服务成功..."<<"\n";
               break;
            } 
             m->DecLoad(); // 减少主机负载
         }

         else
         {
           LOG(ERROR)<<"选择当前请求主机失败"<<id<<" "<<m->_ip<<":"<<m->_prot<<"\n";
           _load_balance.OfflineMachine(id);//把该主机离线
           _load_balance.ShowMachines();//test
         }

         }
         
      }
   };
}